#!/usr/bin/perl -w

#########################################################################
#									#
# Name:		debian (Perl script)					#
#									#
# Completed:	25th November, 2006.					#
#									#
# Updated:	23rd November, 2010.					#
#									#
# Author:	Simon Edwards, Advantsys Computer Services Ltd.		#
#									#
# Arguments:	-V		Print codemgr packaging type - 		#
#				currently set to 2.			#
#									#
# Arguments:	--tag T		The tagged version of the software to	#
#				create the package of.			#
#		--cfgfile	Package configuration file to use if	#
#				default pkg/pkgconf is not suitable.	#
#		--verbose	Verbose mode.				#
#		--name		Override the name of the package to	#
#				create.					#
#		--indir		Find sources in specified directory.	#
#		--overwrite	Overwrite the package if it already	#
#				exists.					#
#									#
# Purpose:	This creates a Debian .deb package based on the cm2	#
#		code management system. 				#
#									#
# Limitations:	1 - No use of the more complex features of .DEB's are	#
#		    included. This will create "binary-only" packages.	#
#									#
# Environment:	CODEMGR_LIBS	- Where cm2 is currently installed.	#
#		PROJECTS_ROOT   - Where project source code is kept.	#
#									#
# Exit Codes:	0  - Package created successfully / all files in 	#
#		     version control system.				#
#		>1 - An error has occurred / not all files under 	#
#		     version control are checked back in.		#
# 									#
#########################################################################

BEGIN {
	push @INC,$ENV{CODEMGR_LIBS} if exists($ENV{CODEMGR_LIBS});
}

use English;
use Cwd;
use Data::Dumper;
use Getopt::Long;
use File::Basename;
use codemgr2 qw(logmsg warnmsg errormsg get_project make_dir_path remove_dir_path copyfile);
use File::Temp qw(tempfile tempdir);

$ARG_version=0;
$ARG_verbose=0;
$ARG_tag=undef;
$ARG_preview=0;
$ARG_overwrite=0;
$ARG_tmpdir=undef;
$ARG_cfgfile="pkg/pkgconf";
$ARG_cfgfile="pkg/pkgconf";
$ARG_project=undef;
$ARG_indir=undef;
$ARG_name=undef;

$_PRG=basename($0);
$_SPC=" " x length($_PRG);

if(substr($0,0,1) eq "." || substr($0,0,1) eq ".") {
	$BINPATH=dirname($0);
} else {
	$BINPATH="";
}

if(@ARGV && $ARGV[0] eq "DESC") {
	print "Debian/Ubuntu .deb package\n";
	exit(0);
}

sub usage {
	print "
       $_PRG  --tag T             - Generate Debian package based on tag
       $_SPC [--cfgfile C] [--verbose] [--tp2bin B] [--name N] [--indir D]
       $_SPC [--overwrite]
";
	exit(1);
}

sub get_dir_matches($$) {
my ($dir,$pat)=@_;
my ($dfd,@m);

	$pat =~ s/\./\\./g;
	$pat =~ s/\*/.*/g;

	opendir($dfd,$dir) or return();
	@m=grep {-f "$dir/$_" && /^$pat$/} readdir($dfd);
	closedir($dfd);
	return (@m);
}

#########################################################################
# Make sure the PROJECTS_ROOT directory exists...			#
#########################################################################

if(!exists($ENV{PROJECTS_ROOT})) {
	errormsg("Required environment variable \"PROJECTS_ROOT\" is not set.",3);
}

$PROJECTS_ROOT=$ENV{PROJECTS_ROOT};

if(! -d $ENV{PROJECTS_ROOT}) {
	errormsg("\"PROJECTS_ROOT\" directory \"$PROJECTS_ROOT\" does not exist.",2);
}

if(!GetOptions(
	"V"		=> \$ARG_version,
	"preview"	=> \$ARG_preview,
	"overwrite"	=> \$ARG_overwrite,
	"verbose"	=> \$ARG_verbose,
	"tag=s"		=> \$ARG_tag,
	"project=s"	=> \$ARG_project,
	"tmpdir=s"	=> \$ARG_tmpdir,
	"indir=s"	=> \$ARG_indir,
	"cfgfile=s"	=> \$ARG_cfgfile,
	"name=s"	=> \$ARG_name,
	)) {
	usage;
}

if($ARG_version) {
	print "2\n";
	exit(0);
}

#########################################################################
# Issue usage if the manadatory --tag and --name are not specified.	#
#########################################################################


usage if !defined($ARG_tag);
usage if !defined($ARG_name);

#########################################################################
# Set the default package configuration file to pkg/pkgconf.		#
#########################################################################

if(!defined($ARG_cfgfile)) {
	$ARG_cfgfile="pkg/pkgconf";
	logmsg("Defaulted \"cfgfile\" to \"$ARG_cfgfile\".");
}

#########################################################################
# Load in the configuration details....					#
#########################################################################

if(!open($fd,$ARG_cfgfile)) {
	errormsg("Unable to read configuration file \"$ARG_cfgfile\" - aborting.");
	exit(1);
}

%cfginfo=();
while(<$fd>) {
	chomp;
	@F=split;
	next if ! /^[A-Z]/;
	$F[0] =~ s/:.*$//;
	$ck=$F[0];
	$cfginfo{$ck}=[] if !exists($cfginfo{$ck});
	s/^[A-Z_0-9]+:\s*//;
	push @{$cfginfo{$ck}},$_;
}
close($fd);

if(!exists($cfginfo{DESCRIPTION})) {
	$cfginfo{DESCRIPTION}=["N/A"];
}

$cfginfo{NAME}=[$ARG_name];
$PKG_NAME=$cfginfo{NAME}->[0];

#########################################################################
# This script is executed from the top level directory of the checked 	#
# out version that is being packaged, so if it has a build script,	#
# then run it now, from the current directory.				#
#########################################################################

if(exists($cfginfo{BUILD})) {
	foreach $sc (@{$cfginfo{BUILD}}) {
		if(-f $sc) {
			chmod 0555,$sc;
			logmsg("Executing build script: $sc");
			system($sc);
			$rc=$?>>8;
			if($rc) {
				warnmsg("Script $sc RC=$rc");
			}
		}
	}
}

#########################################################################
# Generate a temporary configuration file name, unless we are running	#
# in preview mode where in which case the package configuration is	#
# sent to the screen.							#
#########################################################################

if(!$ARG_preview) {
	# Create a temporary directory firstly...			#

	$tmp_dir_name=tempdir( CLEANUP => 1 );
	if(!defined($tmp_dir_name)) {
		errormsg("Unable to create safe temporary directory - aborting.");
		exit(1);
	}
	if(!mkdir "$tmp_dir_name/DEBIAN") {
		errormsg("Unable to create DEBIAN sub-directory: $!");
		exit(1);
	}

	$control_file="$tmp_dir_name/DEBIAN/control";
	open($fd,">$control_file");
} else {
	$fd=*STDOUT;
}

#########################################################################
# Generate the 'control' file, postinst and prerm files ... if the 	#
# generic packaging indicate postrm and/or preinstall scripts warnings	#
# will be issued.							#
#########################################################################

print $fd "Package: $PKG_NAME\n";
print $fd "Version: $ARG_tag\n";
print $fd "Maintainer: N/A\n";
print $fd "Description: N/A\n";
## TODO: print $fd "Section: blah\n";
print $fd "Priority: Optional\n";
if(exists($cfginfo{ARCHITECTURE}) && uc($cfginfo{ARCHITECTURE}->[0]) ne "GENERIC") {
	print $fd "Architecture: $cfginfo{ARCHITECTURE}->[0]\n";
} else {
	print $fd "Architecture: all\n";
}
## TODO: Does debian support OS ??

#########################################################################
# List any dependencies if defined as part of the generic package.	#
#########################################################################

if(exists($cfginfo{DEB_DEPENDS_ON})) {
	my $s="Depends: ";
	my @p=();
	foreach $cpkg (@{$cfginfo{DEB_DEPENDS_ON}}) {
		@F=split(/,/,$cpkg);
		if(!exists($F[1])) {
			push @p,$F[0];
		} else {
			push @p,"$F[0] (>=$F[1])";
		}
	}
	print $fd "$s ",join(",",@p),"\n";
} elsif(exists($cfginfo{DEPENDS_ON})) {
	my $s="Depends: ";
	my @p=();
	foreach $cpkg (@{$cfginfo{DEPENDS_ON}}) {
		@F=split(/,/,$cpkg);
		if(!exists($F[1])) {
			push @p,$F[0];
		} else {
			push @p,"$F[0] (>=$F[1])";
		}
	}
	print $fd "$s ",join(",",@p),"\n";
}
close($fd);

#########################################################################
# The postinstall, preremove, postremove and preinstall scripts are 	#
# copied to postinst, prerm, postrm and preinst.			#
#########################################################################

%map=(
	PREINSTALL	=> "preinst",
	POSTINSTALL	=> "postinst",
	PREREMOVE	=> "prerm",
	POSTREMOVE	=> "postrm",
);

for $cc (keys %map) {
	if(exists($cfginfo{$cc})) {
		my ($bytes,$err)=copyfile($cfginfo{$cc}->[0],"$tmp_dir_name/DEBIAN/$map{$cc}");
		if(defined($err)) {
			errormsg("Unable to copy file '$cfginfo{$cc}->[0]' to temporary directory.");
			exit(1);
		}
		chmod 0755,"$tmp_dir_name/DEBIAN/$map{$cc}";
	}
}

#########################################################################
# Now generate the control.tar.gz file based on the contents of the	#
# temporary directory already populated above.				#
#########################################################################

# $tmp_dir_name2=tempdir( CLEANUP => 1 );
# if(!defined($tmp_dir_name2)) {
	# errormsg("Unable to create safe temporary directory - aborting.");
	# exit(1);
# }

# $cd=cwd;
# chdir($tmp_dir_name);
# $r=`gtar cf $tmp_dir_name2/control.tar.gz . 2>&1 >/dev/null`;
# if($?>>8) {
	# chomp $r;
	# errormsg("Unable to create 'control.tar.gz' file:");
	# errormsg($r);exit(1);
# }

#########################################################################
# Now the control file has been created we need to generate the 	#
# data.tar.gz file.							#
# We do this by populating the first temporary directory with the	#
# files in this case - relatively of course...				#
#########################################################################

#########################################################################
# Firstly empty all files in the directory...				#
#########################################################################

# chdir($tmp_dir_name);
# @F=();
# opendir($dff,".");
# while($c=readdir($dff)) {
	# next if ! -f $c;
	# push @F,$c;
# }
# closedir($dff);
# unlink $_ foreach(@F);

#########################################################################
# Populate the temporary directory with the FILE entries...		#
#########################################################################

# chdir($cd);
if(exists($cfginfo{FILE})) {
	#################################################################
	# Stuff all the dir entries into the file...			#
	#################################################################
	foreach $cfile (
		sort {	my (@c,@d); @c=split(/\s+/,$a); @d=split(/\s+/,$b);
			length($c[0]) <=> length($d[0]);
		} @{$cfginfo{FILE}}) {
		@F=split(/\s+/,$cfile);
		if(@F!=2) {
			errormsg("Malformed 'FILE:' line '$cfile' - aborting",1);
		}
		@F2=split(/,/,$F[1]);
		if($F2[0] eq "") {
			$F2[0]=dirname($F[0]);
		}
		$volatile=$configfile=0;
		if($F2[0] =~ /^volatile$/i) {
			$volatile=1;
			shift @F2;
		}
		if($F2[0] =~ /^configfile$/i) {
			$configfile=1;
			shift @F2;
		}
		#########################################################
		# Handle patterning matching ...			#
		# creating directories if they do not exist...		#
		#########################################################

		@m=get_dir_matches(dirname($F[0]),basename($F[0]));
		if(@m) {
			if(! -d "$tmp_dir_name/$F2[0]") {
				make_dir_path("$tmp_dir_name/$F2[0]");
			}
		}
		foreach $ccfile (@m) {
			my ($bytes,$err)=copyfile(dirname($F[0])."/$ccfile","$tmp_dir_name/$F2[0]/$ccfile");
			if(defined($err)) {
				errormsg("Unable to copy file '" . dirname($F[0]) . "/$ccfile to temporary directory [$tmp_dir_name/$F2[0]/$ccfile].");
				exit(1);
			}

			#################################################
			# If running as root set the correct ownerships	#
			# on the file just copied.			#
			#################################################
			
			if($<==0) {
				if($F2[1] ne "") {
					my $uid=getpwnam($F2[1]);
					if(!defined($uid)) {
						errormsg("Unable to map name '$F2[1]' to UID - ignoring.");
					} else {
						chown $uid,-1,"$tmp_dir_name/$F2[0]/$ccfile";
					}
				}

				if($F2[2] ne "") {
					my $gid=getgrnam($F2[2]);
					if(!defined($gid)) {
						errormsg("Unable to map name '$F2[2]' to GID - ignoring.");
					} else {
						chown -1,$gid,"$tmp_dir_name/$F2[0]/$ccfile";
					}
				}
			}
			if($F2[3] ne "") {
				chmod oct($F2[3]),"$tmp_dir_name/$F2[0]/$ccfile";
			}
			# print $fd " configfile=\"y\"" if $configfile;
		}
	}

	#################################################################
	# The tmp_dir_name has been populated with everything based on	#
	# the package configuration, so generate the data.tar.gz file	#
	# now.								#
	#################################################################
}

# chdir($tmp_dir_name);
# $r=`gtar cf $tmp_dir_name2/data.tar.gz . 2>&1 >/dev/null`;
# if($?>>8) {
	# chomp $r;
	# errormsg("Unable to create 'data.tar.gz' file:");
	# errormsg($r);exit(1);
# }

#########################################################################
# Create the version file in this directory as well...			#
#########################################################################

if(!open($fd,">$tmp_dir_name/DEBIAN/debian-binary") ) {
	errormsg("Unable to create the 'debian-binary' file: $!");
	exit(1);
}
print $fd "2.0\n";
close($fd);

#########################################################################
# Now all the files are in the temporary directory, whilst the DEBIAN	#
# sub-directory is also present - so generate the package...		#
#########################################################################

$debname="${PKG_NAME}_${ARG_tag}";
if(exists($cfginfo{ARCHITECTURE}) && uc($cfginfo{ARCHITECTURE}->[0]) ne "GENERIC") {
	$debname.="_$cfginfo{ARCHITECTURE}->[0]";
}

chdir "/tmp";
# print "tmp_dir_name=$tmp_dir_name\n";
$r=`dpkg-deb -b $tmp_dir_name $debname.deb 2>&1`;
if($?>>8) {
	errormsg("Unable to create package '$debname.deb':");
	errormsg($r);
	exit(1);
}

$ARG_verbose=1;
logmsg("Package created as /tmp/$debname.deb.");
exit(0);
